package nl.rug.snitter.db;


import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;

import nl.rug.snitter.PMF;
import nl.rug.snitter.message.Message;
import nl.rug.snitter.message.MessageManager;
import nl.rug.snitter.message.MessagePropagation;
import nl.rug.snitter.site.SimpleUserInfo;
import nl.rug.snitter.user.User;

public abstract class CommonDbCommands
{
	//	private static final int MaxRetries= 5;
	//	private static final float RetrieSleepTimeInSeconds= 2;

	@SuppressWarnings("unchecked")
	static public User getUser(Long id, boolean useTransaction, boolean closeManager, PersistenceManager pm)
	{
		if(pm == null)
		{
			pm = PMF.get().getPersistenceManager();
		}
		if(useTransaction)
		{
			Transaction tx= pm.currentTransaction();
			try
			{
				tx.begin();
				User user= null;
				String query= "select from " + User.class.getName() + " where id == " + id.toString();
				List<User> users= (List<User>)pm.newQuery(query).execute();
				if(users.size() != 0)
				{
					user= users.get(0);
					try
					{
						user.deSerializeFields();
					}
					catch (IOException e)
					{
						e.printStackTrace();
					}
					catch (ClassNotFoundException e) 
					{
						e.printStackTrace();
					}
				}	
				tx.commit();
				return user;
			}
			finally
			{
				if(tx.isActive())
				{
					tx.rollback();
				}
				if(closeManager)
				{
					pm.close();
				}
			}
		}
		else
		{
			try
			{
				User user= null;
				String query= "select from " + User.class.getName() + " where id == " + id.toString();
				List<User> users= (List<User>)pm.newQuery(query).execute();
				if(users.size() != 0)
				{
					user= users.get(0);
					try
					{
						user.deSerializeFields();
					}
					catch (IOException e)
					{
						e.printStackTrace();
					}
					catch (ClassNotFoundException e) 
					{
						e.printStackTrace();
					}
				}	
				return user;
			}
			finally
			{
				if(closeManager)
				{
					pm.close();
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	static public User findUserBy(String fieldName, String data, boolean useTransaction, boolean closeManager)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		User user= null;
		if(useTransaction)
		{
			Transaction tx= pm.currentTransaction();
			try
			{
				tx.begin();
				String query= "select from " + User.class.getName() + " where " + fieldName + " == " + data;
				//Query query= pm.newQuery(User.class, fieldName + " " + " == " + data);//"select from " + User.class.getName() + " where " + fieldName + " == " + data;
				List<User> users= (List<User>)pm.newQuery(query).execute();
				//pm.newQuery(User.class);
				//List<User> users2= (List<User>)pm.newQuery(query).execute();
				tx.commit();
				if(users.size() > 0)
				{
					user= users.get(0);
					try
					{
						user.deSerializeFields();
					}
					catch (IOException e)
					{
						e.printStackTrace();
					}
					catch (ClassNotFoundException e) 
					{
						e.printStackTrace();
					}
				}
				return user;
			}
			finally
			{
				if(tx.isActive())
				{
					tx.rollback();
				}
				if(closeManager)
				{
					pm.close();
				}
			}
		}
		else
		{
			try
			{
				Query query= pm.newQuery(User.class, fieldName + " " + " == " + data);
				List<User> users= (List<User>)pm.newQuery(query).execute();
				if(users.size() != 0)
				{
					user= users.get(0);
					try
					{
						user.deSerializeFields();
					}
					catch (IOException e)
					{
						e.printStackTrace();
					}
					catch (ClassNotFoundException e) 
					{
						e.printStackTrace();
					}
				}
				return user;
			}
			finally
			{
				if(closeManager)
				{
					pm.close();
				}
			}
		}
	}

	static public void AddUser(User user)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		try
		{
			tx.begin();
			//JDOHelper.makeDirty(user, "incomingFollowRequest");
			try
			{
				user.serializeFields();
			}
			catch(IOException e)
			{
				e.printStackTrace();
			}
			pm.makePersistent(user);
			tx.commit();
		}
		finally
		{
			if(tx.isActive())
			{
				tx.rollback();
			}
			pm.close();
		}
	}

	static public void UpdateUser(User user)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			try
			{
				user.serializeFields();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
			pm.makePersistent(user);
		}
		finally
		{
			pm.close();
		}
	}

	/**
	 * This function does not close the persistenceManager.
	 * @param user
	 * @param pm
	 */
	static public void UpdateUser(User user, PersistenceManager pm)
	{
		try
		{
			user.serializeFields();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		pm.makePersistent(user);
	}

	static public void DeleteUser(User user)
	{
		//remove followers and following and incoming following request
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		Long userId= user.getId();
		try
		{
			tx.begin();
			User tempUser;
			//remove following
			for(Long tempId: user.getFollowing())
			{
				tempUser= getUser(tempId, false, false, pm);
				if(tempUser != null)
				{
					tempUser.getFollowedBy().remove(userId);
				}
			}
			//remove followed by
			for(Long tempId: user.getFollowedBy())
			{
				tempUser= getUser(tempId, false, false, pm);
				if(tempUser != null)
				{
					tempUser.getFollowing().remove(userId);
				}
			}
			//remove incoming following request
			//?????
			tx.commit();
		}
		finally
		{
			pm.close();
		}
	}

	/**
	 * This function will also update the user with the new following id.
	 * @param userThatIsFollowing
	 * @param idOfWhoIsFollowedBy
	 */
	static public void AddFollowerToUser(User userThatIsFollowing, Long idOfWhoIsFollowedBy, MessagePropagation propagation)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		User tempUser = null;
		try
		{
			tx.begin();
			tempUser= getUser(idOfWhoIsFollowedBy, false, false, pm);
			tx.commit();
		}
		finally
		{
			if(tx.isActive())
			{
				tx.rollback();
				pm.close();
			}
			else
			{
				pm.close();
				pm= PMF.get().getPersistenceManager();
				tx= pm.currentTransaction();
				try
				{
					tx.begin();
					userThatIsFollowing.addFollowing(idOfWhoIsFollowedBy, propagation);
					try
					{
						userThatIsFollowing.serializeFields();
					}
					catch(IOException e)
					{
						e.printStackTrace();
					}
					pm.makePersistent(userThatIsFollowing);
					tx.commit();
				}
				finally
				{
					if(tx.isActive())
					{
						tx.rollback();
						pm.close();
					}
					else
					{
						pm.close();
						pm= PMF.get().getPersistenceManager();
						tx= pm.currentTransaction();
						try
						{
							tx.begin();
							tempUser.addFollowedBy(userThatIsFollowing.getId());
							try
							{
								tempUser.serializeFields();
							}
							catch (IOException e) 
							{
								e.printStackTrace();
							}
							pm.makePersistent(tempUser);
							tx.commit();
						}
						finally
						{
							if(tx.isActive())
							{
								tx.rollback();
							}
							pm.close();
						}
					}
				}
			}
		}
	}

	static public void AddMessageToUser(Long id, Long msgId)
	{
		//don't forget to propagate
		User user= getUser(id, true, true, null);
		user.addMsg(msgId);
		UpdateUser(user);
		propagateMsg(user, msgId);
	}

	static public void AddMessageToUser(User user, Long msgId)
	{
		//don't forget to propagate
		user.addMsg(msgId);
		UpdateUser(user);
		propagateMsg(user, msgId);
	}

	/**
	 * This function returns the id when a msg is saved otherwise it will return -1
	 * @param msg
	 * @return
	 */
	static public Long saveMessage(Message msg)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		try
		{
			tx.begin();
			pm.makePersistent(msg);
			tx.commit();
			return msg.getId();
		}
		finally
		{
			if(tx.isActive())
			{
				tx.rollback();
				pm.close();
			}
		}
	}

	@SuppressWarnings("unchecked")
	static public Message getMsg(Long msgId)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		List<Message> msg= null;
		try
		{
			tx.begin();
			String query= "select from " + Message.class.getName() + " where " + "id" + " == " + msgId;
			msg= (List<Message>)pm.newQuery(query).execute();
			tx.commit();
			if(tx.isActive())
			{
				return null;
			}
			else
			{
				return msg.size() >= 1 ? msg.get(0) : null;
			}
		}
		finally
		{
			if(tx.isActive())
			{
				tx.rollback();
				pm.close();
			}
		}		
	}

	@SuppressWarnings("unchecked")
	/**
	 * This function will remove the msg from the user and from the db
	 */
	static public void removeMsg(Long msgId, User user)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		List<Message> msgs= null;
		try
		{
			tx.begin();
			String query= "select from " + Message.class.getName() + " where " + "id" + " == " + msgId;
			msgs= (List<Message>)pm.newQuery(query).execute();
			pm.deletePersistentAll(msgs);
			tx.commit();
		}
		finally
		{
			if(tx.isActive())
			{
				tx.rollback();
				pm.close();
			}
			else
			{
				pm.close();
				pm= PMF.get().getPersistenceManager();
				tx= pm.currentTransaction();
				user.getMessageManager().removeMsg(msgId);
				try
				{
					tx.begin();
					UpdateUser(user, pm);
					tx.commit();
				}
				finally
				{
					if(tx.isActive())
					{
						tx.rollback();
						pm.close();
					}
				}
			}
		}
	}

	static public void removeFollowing(User user, User followingUser)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		try
		{
			//remove the following user from the user
			tx.begin();
			user.getFollowing().remove(followingUser.getId());
			UpdateUser(user, pm);
			tx.commit();
		}
		finally
		{
			if(tx.isActive())
			{
				tx.rollback();
				pm.close();
			}
		}
	}

	static public void removeFollowedBy(User user, User userFollowedBy)
	{
		//remove followed by from following
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		try
		{
			tx.begin();
			user.getFollowedBy().remove(userFollowedBy.getId());
			UpdateUser(user, pm);
			tx.commit();
		}
		finally
		{
			if(tx.isActive())
			{
				tx.rollback();
				pm.close();
			}
		}
	}

	@SuppressWarnings("unchecked")
	static public ArrayList<SimpleUserInfo> search(String searchBy, String searchFor)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx= pm.currentTransaction();
		List<User> users = null;
		try
		{
			tx.begin();
			String query= "select from " + User.class.getName() + " where " + searchBy + " == " + searchFor;
			users= (List<User>)pm.newQuery(query).execute();
			tx.commit();
			if(tx.isActive())
			{
				tx.rollback();
				pm.close();
				return null;
			}
			else
			{
				ArrayList<SimpleUserInfo> simpleUsers= new ArrayList<SimpleUserInfo>();
				for(User user : users)
				{
					simpleUsers.add(user.convertToSimpleUserInfo());
				}
				return simpleUsers;
			}
		}
		finally
		{
			if(tx.isActive())
			{
				tx.rollback();
				pm.close();
			}		
		}
	}

	@SuppressWarnings("unchecked")
	/**
	 * This function will get the list of new msg that a user should see (new msg of him self plus of the persons he is following)
	 */
	static public List<Message> getListOfNewMsg(int numberOfMsg, Long startId, User user)
	{
		List<Long> users= user.getFollowing();
		List<Message> tempMsg= null;
		ArrayList<Message> msgs= new ArrayList<Message>();
		String query= null;
		PersistenceManager pm= PMF.get().getPersistenceManager();
		if(users.size() >= 1)
		{
			//userId == " + user.getId() + " && " ;
			for(Long tempId : users)
			{
				query= "select from " + Message.class.getName() + " where userId == " + tempId;
				tempMsg= (List<Message>)pm.newQuery(query).execute();
				msgs.addAll(tempMsg);
			}
		}
		query= "select from " + Message.class.getName() + " where userId == " + user.getId();
		tempMsg= (List<Message>)pm.newQuery(query).execute();
		msgs.addAll(tempMsg);
		Collections.sort(msgs, new MessageIdComparator());
		if(startId > 0)
		{
			int index= Collections.binarySearch(msgs, startId, new Comparator(){
				public int compare(Object o1, Object o2)
				{
					if(o1 instanceof Message)
					{
						Message m= (Message)o1;
						if(m.getId() == (Long)o2)
						{
							return 0;
						}
						else if(m.getId() > (Long)o2)
						{
							return 1;
						}
						else
						{
							return -1;
						}
					}
					else
					{
						Message m= (Message)o2;
						if(m.getId() == (Long)o1)
						{
							return 0;
						}
						else if(m.getId() > (Long)o1)
						{
							return 1;
						}
						else
						{
							return -1;
						}
					}
				}
			});
			if(index > 0)
			{
				return msgs.subList(index, (msgs.size() < numberOfMsg + index ? numberOfMsg + index : msgs.size()));
			}
			else
			{
				return new ArrayList<Message>();
			}
		}
		else
		{
			return msgs.size() < numberOfMsg  ? msgs : msgs.subList(0,numberOfMsg);
		}
	}

	@SuppressWarnings("unchecked")
	static public List<Message> getPublicListOfNewMsg(int size)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		String query= "select from " + Message.class.getName();
		List<Message> tempMsg= (List<Message>)pm.newQuery(query).execute();
		Collections.sort(tempMsg, new MessageIdComparator());
		if(size > 0)
		{
			return tempMsg.subList(0, size > tempMsg.size() ? tempMsg.size() : size);
		}
		else
		{
			return tempMsg.subList(0, 20 > tempMsg.size() ? tempMsg.size() : 20);
		}
	}

	static private void propagateMsg(User user, Long msgId)
	{
		User tempUser= null;
		for(Long id : user.getFollowedBy())
		{
			try
			{
				tempUser= getUser(id, true, true, null);
				for(MessagePropagation tempPropagation : tempUser.getMessageManager().getMsgPropagation(user.getId()))
				{
					if(tempPropagation != null)
					{
						tempPropagation.execute(tempUser, user, msgId);
					}
				}
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}

	@SuppressWarnings("unchecked")
	static public void clearDb()
	{
		PersistenceManager pm= PMF.get().getPersistenceManager();
		try
		{
			List<User> t= (List<User>)pm.newQuery(User.class).execute();
			pm.deletePersistentAll(t);
			List<MessageManager> t2= (List<MessageManager>)pm.newQuery(MessageManager.class).execute();
			pm.deletePersistentAll(t2);
			List<Message> t3= (List<Message>)pm.newQuery(Message.class).execute();
			pm.deletePersistentAll(t3);
			//			List<SiteMessagePropagation> t3= (List<SiteMessagePropagation>)pm.newQuery(SiteMessagePropagation.class).execute();
			//			pm.deletePersistentAll(t3);
		}
		finally
		{
			pm.close();
		}
	}

	static private class MessageIdComparator implements Comparator<Message>
	{
		public int compare(Message m1, Message m2)
		{
			if(m1.getUserId() == m2.getId())
			{
				return 0;
			}
			else if(m1.getId() < m2.getId())
			{
				return 1;
			}
			else
			{
				return -1;
			}
		}
	}
}
